package com.pluszg.nutrition.common.execption;


import com.pluszg.nutrition.common.exception.PlusException;
import com.pluszg.nutrition.common.resp.CommonResponse;
import com.pluszg.nutrition.common.resp.ResponseCode;
import com.pluszg.nutrition.common.utils.json.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static java.util.stream.Collectors.toList;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@ResponseStatus(HttpStatus.OK)
@Slf4j
public class GlobalExceptionHandler {

    /**
     *所有已知异常
     */
    public static final Class[] ALL_KNOWN_EXCEPTION = new Class[]{
            PlusException.class,//自定义异常
            MethodArgumentNotValidException.class,//参数校验出错
            MethodArgumentTypeMismatchException.class,//@PathVariable 参数绑定错误
            ConstraintViolationException.class,//参数校验出错
            HttpMessageNotReadableException.class,//@RequestBody 参数绑定错误
            MissingServletRequestParameterException.class,//@RequestParam 参数绑定错误
            MissingServletRequestPartException.class,//@RequestPart 参数绑定错误
            UndeclaredThrowableException.class,//sentinel网关限流异常
            HttpRequestMethodNotSupportedException.class,//不支持的http请求方式

            NoHandlerFoundException.class//未找到对应的controller 404
    };

    /**
     * 所有已知的异常类名称
     */
    public static final List<String> ALL_KNOWN_EXCEPTION_NAME;

    static{
        ALL_KNOWN_EXCEPTION_NAME = Arrays.stream(ALL_KNOWN_EXCEPTION).map(Class::getName)
                .collect(toList());
    }

    @ExceptionHandler(value = PlusException.class)
    public CommonResponse<Object> plusExceptionHandler(PlusException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(e.getCode(), e.getMessage(), e.getObject());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResponse<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        String message = e.getMessage();
        FieldError fieldError = e.getBindingResult().getFieldError();
        if (fieldError != null && StringUtils.isNotBlank(fieldError.getDefaultMessage())) {
            message = fieldError.getDefaultMessage();
        }
        return this.writeToResponse(ResponseCode.INVALID_PARAM, message, null);
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResponse<String> constraintViolationExceptionHandler(ConstraintViolationException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        String message = e.getMessage();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        if (ObjectUtils.isNotEmpty(violations)) {
            Optional<String> optional = violations.stream().map(ConstraintViolation::getMessage)
                    .findFirst();
            if (optional.isPresent()) {
                message = optional.get();
            }
        }
        return this.writeToResponse(ResponseCode.INVALID_PARAM, message, null);
    }

    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public CommonResponse<String> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, "Required request body is missing or invalid.", null);
    }

    @ExceptionHandler(value = MissingServletRequestPartException.class)
    public CommonResponse<String> missingServletRequestPartExceptionHandler(MissingServletRequestPartException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, String.format("Required request part [%s] is not present or invalid.", e.getRequestPartName()), null);
    }

    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResponse<String> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, String.format("Required parameter [%s] is not present or invalid.", e.getParameterName()), null);
    }

    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public CommonResponse<String> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, String.format("The parameter [%s] is not present or invalid.index=[%s],message=[%s]"
                , e.getName(), e.getParameter().getParameterIndex(), e.getMessage()), null);
    }

    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public CommonResponse<String> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, String.format("Request method [%s] not supported,Please check your request method.", e.getMethod()), null);
    }

    @ExceptionHandler(value = NoHandlerFoundException.class)
    public CommonResponse<String> noHandlerFoundExceptionHandler(NoHandlerFoundException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        return this.writeToResponse(ResponseCode.NOT_FOUND_HANDLER, String.format("Not found the [%s] handler.", e.getHttpMethod().concat(" ").concat(e.getRequestURL())), null);
    }

    @ExceptionHandler(value = UndeclaredThrowableException.class)
    public CommonResponse<String> UndeclaredThrowableExceptionHandler(UndeclaredThrowableException e, HttpServletRequest request) {
        this.printStackTrace(e, request);
        System.out.println(e);
        return this.writeToResponse(ResponseCode.INVALID_PARAM, "达到阈值了，不要在访问了", null);
    }

    private void printStackTrace(Throwable throwable, HttpServletRequest request) {
        boolean isKnownException = Arrays.stream(ALL_KNOWN_EXCEPTION).
                anyMatch(knownExClass -> knownExClass.isAssignableFrom(throwable.getClass()));

        if (isKnownException) {
            Throwable rootCause = ExceptionUtils.getRootCause(throwable);
            log.error("Encounter known exception.httpMethod=[{}],URL=[{}],exceptionName=[{}],errorMsg=[{}]",
                    request.getMethod(), request.getRequestURI(), rootCause.getClass().getName(), rootCause.getMessage());
        } else {
            //未知异常 打印完整的堆栈信息
            log.error("Encounter unknown exception.httpMethod=[{}],URL=[{}],throwable=[{}]", request.getMethod(), request.getRequestURI(), throwable);
            //发送通知
        }
    }

    private <T> CommonResponse<T> writeToResponse(int retCode, String retMsg, T result) {
        CommonResponse<T> response = new CommonResponse<>(retCode, retMsg, result);
        log.debug("\n处理完请求，返回内容：RESPONSE=[\n{}\n]", JsonUtil.format2JsonStr(response));
        return response;
    }

}
